/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.bff.designtime.generator.core.mgrimpl;


import com.inspur.edp.bef.bizentity.operation.BizOperation;
import com.inspur.edp.bef.component.base.GspComponent;
import com.inspur.edp.bef.component.detailcmpentity.bemgr.BEMgrComponent;
import com.inspur.edp.bef.component.detailcmpentity.bemgr.BEMgrMethod;
import com.inspur.edp.bef.component.detailcmpentity.bemgr.BEMgrMethodParameter;
import com.inspur.edp.bff.designtime.generator.VmGeneratorContext;
import com.inspur.edp.bff.designtime.generator.common.IMetadataPool;
import com.inspur.edp.bff.spi.action.actionstatck.VoActionContext;
import com.inspur.edp.caf.generator.baseInfo.ParameterInfo;
import com.inspur.edp.caf.generator.baseInfo.TypeInfo;
import com.inspur.edp.caf.generator.method.ClassMethodGenerator;
import com.inspur.edp.cef.api.RefObject;
import com.inspur.edp.cef.core.entityaction.CefActionStack;
import com.inspur.edp.cef.entity.changeset.Tuple;
import com.inspur.edp.cef.generator.overall.Utils;
import com.inspur.edp.formserver.viewmodel.action.MappedBizAction;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.core.dom.Assignment.Operator;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

//import jdk.internal.org.objectweb.asm.tree.MethodInsnNode;

public class BeActionMethodGenerator extends ClassMethodGenerator {

  private static final String ResultVarName = "result";
  private static final String ConfigSectionName = "ConfigSection";
  private static final String VariableName = "beLcp";
  private MappedBizAction action;
  private VmGeneratorContext vmGenCtx;
  private IMetadataPool pool;
  private BEMgrComponent compMetadata;
  private BEMgrMethod method;
  private java.util.ArrayList<Tuple<String,TypeInfo>> parList;
  private BizOperation bizMgrAction;

  public BeActionMethodGenerator(MappedBizAction action, VmGeneratorContext vmGenCtx,
      IMetadataPool pool) {
    this.action = action;
    this.vmGenCtx = vmGenCtx;
    //this.interfaceType = interfaceType;
    this.pool = pool;
  }

  @Override
  protected void doInitialize() {
    bizMgrAction = vmGenCtx.getRefBizEntity().getBizMgrActions().getItem(action.getMapping().getTargetObjId());
    if (bizMgrAction == null) {
      throw new RuntimeException(
          "#GspBefError# 映射BE上没有找到对应的MgrAction：" + action.getCode() + "#GspBefError# ");
    }

    GspMetadata metadata = pool
        .getMetadata(bizMgrAction.getComponentPkgName(), bizMgrAction.getComponentId());

      GspComponent comp = (GspComponent) metadata.getContent();
      if (!comp.getComponentType().equals("BEMgrComponent")) {
        throw new RuntimeException("错误的类型");
      }
      compMetadata = (BEMgrComponent) ((comp instanceof BEMgrComponent) ? comp : null);
      method = (BEMgrMethod)comp.getMethod();
  }

  @Override
  protected java.util.ArrayList<ModifierKeyword> getAccessModifiers() {
    return new java.util.ArrayList<ModifierKeyword>(
        java.util.Arrays.asList(new ModifierKeyword[]{ModifierKeyword.PUBLIC_KEYWORD}));
  }

  @Override
  protected String getMethodName() {
    return action.getCode();
  }

  @Override
  protected java.util.ArrayList<ParameterInfo> getParameterCollection() {
    java.util.ArrayList<ParameterInfo> list =new java.util.ArrayList<ParameterInfo>();
    for (Tuple<String,TypeInfo> item:preparePars())
    {
      ParameterInfo tempVar = new ParameterInfo();
      tempVar.setParamName(item.getItem1());
      tempVar.setParamType(item.getItem2());
      list.add(tempVar);
    }

    return list;
  }

  private java.util.ArrayList<Tuple<String,TypeInfo>> preparePars() {
    java.util.ArrayList<Tuple<String,TypeInfo>> result = new java.util.ArrayList<Tuple<String,TypeInfo>>();
    if (parList == null) {
      if(method.getParams() != null) {
        for (Object obj : method.getParams()) {
          BEMgrMethodParameter par = (BEMgrMethodParameter)obj;
          result.add(new Tuple(par.getParamCode(), Utils.getNativeType(par, vmGenCtx)));
        }
        parList = result;
      }
    }
    return parList;
  }

  //C# TO JAVA CONVERTER TODO TASK: Lambda expressions and anonymous methods are not converted by C# to Java Converter:
  @Override
  protected TypeInfo getReturnType() {
    return Utils.getReturnType(method, vmGenCtx);
  }

  //protected override string getGeneratedReturnType() { return method.GetGeneratedReturnType(vmGenCtx); }
  @Override
  protected Block buildMethodBody() {
    addImport(VoActionContext.class.getTypeName());
    java.util.ArrayList<Statement> statements = new java.util.ArrayList<Statement>();

    statements.add(getCreateVoActionContextStatement());
    statements.add(getPushContextToStackStatement());
    statements.add(getTryStatement());

    Block block = ast.newBlock();
    block.statements().addAll(statements);
    return block;
  }

  private Statement getCreateVoActionContextStatement() {
    VariableDeclarationFragment variableDeclarationFragment=ast.newVariableDeclarationFragment();
    variableDeclarationFragment.setName(ast.newSimpleName("voActionContext"));
    ClassInstanceCreation classInstanceCreation=ast.newClassInstanceCreation();
    classInstanceCreation.setType(ast.newSimpleType(ast.newSimpleName("VoActionContext")));
    classInstanceCreation.arguments().add(getVoIDStatement());
    StringLiteral stringLiteralActionID=ast.newStringLiteral();
    stringLiteralActionID.setLiteralValue(action.getID());
    classInstanceCreation.arguments().add(stringLiteralActionID);
    StringLiteral stringLiteralActionCode =ast.newStringLiteral();
    stringLiteralActionCode.setLiteralValue(action.getCode());
    classInstanceCreation.arguments().add(stringLiteralActionCode);
    classInstanceCreation.arguments().add(ast.newNullLiteral());
    variableDeclarationFragment.setInitializer(classInstanceCreation);

    VariableDeclarationStatement variableDeclarationStatement=ast.newVariableDeclarationStatement(variableDeclarationFragment);
    variableDeclarationStatement.setType(ast.newSimpleType(ast.newSimpleName("VoActionContext")));
    return variableDeclarationStatement;
  }

  private Object getVoIDStatement() {
    MethodInvocation methodInvocation=ast.newMethodInvocation();
    methodInvocation.setName(ast.newSimpleName("getVoId"));
    MethodInvocation methodInvocation1=ast.newMethodInvocation();
    methodInvocation1.setName(ast.newSimpleName("getContext"));
    methodInvocation.setExpression(methodInvocation1);
    return methodInvocation;
  }

  private Statement getPushContextToStackStatement() {
    MethodInvocation methodInvocation=ast.newMethodInvocation();
    methodInvocation.setName(ast.newSimpleName("push"));
    methodInvocation.setExpression(ast.newSimpleName("CefActionStack"));
    methodInvocation.arguments().add(ast.newSimpleName("voActionContext"));
    return ast.newExpressionStatement(methodInvocation);
  }

  private Statement getTryStatement() {
    TryStatement tryStatement=ast.newTryStatement();

    java.util.ArrayList<Statement> statements = new java.util.ArrayList<Statement>();
    statements.add( getCreateLcpStatement());
    statements.addAll(action.getIsAutoSave()? getTransStatementWithSave() : getTransStatement());

//C# TO JAVA CONVERTER TODO TASK: There is no equivalent to implicit typing in Java:
    MethodInvocation procesResponseInvocation =ast.newMethodInvocation();
    procesResponseInvocation.setName(ast.newSimpleName("processResponse"));
    statements.add(ast.newExpressionStatement(procesResponseInvocation));

    if (getReturnType().getTypeName() .equals("void")==false) {
      ReturnStatement returnStatement = ast.newReturnStatement();
      returnStatement.setExpression(ast.newSimpleName(ResultVarName));
      statements.add(returnStatement);
    }
    Block block=ast.newBlock();
    block.statements().addAll(statements);
    tryStatement.setBody(block);

    MethodInvocation methodInvocation=ast.newMethodInvocation();
    methodInvocation.setName(ast.newSimpleName("pop"));
    methodInvocation.setExpression(ast.newSimpleName("CefActionStack"));
    addImport(CefActionStack.class.getTypeName());
    Block finallyBlock =ast.newBlock();
    finallyBlock.statements().add(ast.newExpressionStatement(methodInvocation));
    tryStatement.setFinally(finallyBlock);
    return tryStatement;
  }

  private TypeInfo getBeManagerType() {
    TypeInfo result = new TypeInfo(String.format("I%1$sService", vmGenCtx.getRefBizEntity().getCode()));
    result.setTypePackageName(vmGenCtx.getRefBizEntity().getApiNamespace().getDefaultNamespace());
    addImport(vmGenCtx.getRefBizEntity().getApiNamespace().getDefaultNamespace()+"."+String.format("I%1$sService", vmGenCtx.getRefBizEntity().getCode()));
    return result ;

  }

  private Statement getCreateLcpStatement() {
    VariableDeclarationFragment variableDeclarationFragment = ast.newVariableDeclarationFragment();
    variableDeclarationFragment.setName(ast.newSimpleName(VariableName));
    CastExpression castBeMgrExpr = ast.newCastExpression();
    castBeMgrExpr.setType(getBeManagerType().getType(ast));
    MethodInvocation getLcpInvoation = ast.newMethodInvocation();
    getLcpInvoation.setName(ast.newSimpleName("getLcp"));
    MethodInvocation getContextInvocation = ast.newMethodInvocation();
    getContextInvocation.setName(ast.newSimpleName("getContext"));
    getLcpInvoation.setExpression(getContextInvocation);
    castBeMgrExpr.setExpression(getLcpInvoation);
    variableDeclarationFragment.setInitializer(castBeMgrExpr);
    VariableDeclarationStatement variableDeclarationStatement = ast.newVariableDeclarationStatement(variableDeclarationFragment);
    variableDeclarationStatement.setType(getBeManagerType().getType(ast));
    return variableDeclarationStatement;
  }

  private List<Statement> getTransStatementWithSave() {
    final String resultArray = "resultArray";
    List<Statement> rez = new ArrayList<>(3);

    List<String> arguments = preparePars().stream().map(item ->
      item.getItem1()).collect(Collectors.toList());

    MethodInvocation bizActionInvocation = ast.newMethodInvocation();
    bizActionInvocation.setName(ast.newSimpleName(bizMgrAction.getCode()));
    bizActionInvocation.setExpression(ast.newSimpleName(VariableName));
    for (String arg : arguments ) {
      bizActionInvocation.arguments().add(ast.newSimpleName(arg));
    }

    if (getReturnType().getTypeName() .equals("void")==false) {
      ParameterizedType refType = ast.newParameterizedType(
          ast.newSimpleType(ast.newName(RefObject.class.getTypeName())));
      refType.typeArguments().add(getReturnType().getType(ast));

      ClassInstanceCreation refTypeCreation = ast.newClassInstanceCreation();
      refTypeCreation.setType(refType);
      refTypeCreation.arguments().add(ast.newNullLiteral());

      VariableDeclarationFragment variableDeclarationFragment = ast.newVariableDeclarationFragment();
      variableDeclarationFragment.setName(ast.newSimpleName(resultArray));
      variableDeclarationFragment.setInitializer(refTypeCreation);//bizActionInvocation
      VariableDeclarationStatement variableDeclarationStatement =ast.newVariableDeclarationStatement(variableDeclarationFragment);
      ParameterizedType refType1 = ast.newParameterizedType(
          ast.newSimpleType(ast.newName(RefObject.class.getTypeName())));
      refType1.typeArguments().add(getReturnType().getType(ast));
      variableDeclarationStatement.setType(refType1);
      variableDeclarationStatement.modifiers().add(ast.newModifier(ModifierKeyword.FINAL_KEYWORD));
      rez.add(variableDeclarationStatement);
    }

//    Runnable r = new Runnable() {
//      @Override
//      public void run() {
//        rez[0] = lcp.xxxx;
//        lcp.save();
//      }
//    };

    VariableDeclarationFragment runnableDeclarationFragment = ast.newVariableDeclarationFragment();
    runnableDeclarationFragment.setName(ast.newSimpleName("run"));
    ClassInstanceCreation instanceRunnable = ast.newClassInstanceCreation();
    instanceRunnable.setType(new TypeInfo(Runnable.class){{setUseFullName(true);}}.getType(ast));
    AnonymousClassDeclaration anonymousRunnable = ast.newAnonymousClassDeclaration();
    MethodDeclaration runMethodDeclaration = ast.newMethodDeclaration();
    runMethodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
    runMethodDeclaration.setReturnType2(new TypeInfo("void").getType(ast));
    runMethodDeclaration.setName(ast.newSimpleName("run"));
    Block runBlock = ast.newBlock();
    Expression statementInRun = bizActionInvocation;
    if (getReturnType().getTypeName() .equals("void")==false){
      Assignment assignment = ast.newAssignment();
      assignment.setLeftHandSide(ast.newQualifiedName(ast.newSimpleName(resultArray), ast.newSimpleName("argvalue")));
      assignment.setOperator(Operator.ASSIGN);
      assignment.setRightHandSide(bizActionInvocation);
      statementInRun = assignment;
    }
    runBlock.statements().add(ast.newExpressionStatement(statementInRun));
    MethodInvocation saveInvocation = ast.newMethodInvocation();
    saveInvocation.setName(ast.newSimpleName("save"));
    runBlock.statements().add(ast.newExpressionStatement(saveInvocation));
    runMethodDeclaration.setBody(runBlock);
    anonymousRunnable.bodyDeclarations().add(runMethodDeclaration);
    instanceRunnable.setAnonymousClassDeclaration(anonymousRunnable);
    runnableDeclarationFragment.setInitializer(instanceRunnable);//bizActionInvocation
    VariableDeclarationStatement runnableDeclarationStatement =ast.newVariableDeclarationStatement(runnableDeclarationFragment);
    runnableDeclarationStatement.setType(new TypeInfo(Runnable.class){{setUseFullName(true);}}.getType(ast));
    rez.add(runnableDeclarationStatement);

    CastExpression castLcpExpression = ast.newCastExpression();
    castLcpExpression.setType(ast.newSimpleType(ast.newName("com.inspur.edp.bef.api.lcp.IStandardLcp")));
    castLcpExpression.setExpression(ast.newSimpleName(VariableName));
    ParenthesizedExpression parenthesizedExpression = ast.newParenthesizedExpression();
    parenthesizedExpression.setExpression(castLcpExpression);
    MethodInvocation atomicallInvoke = ast.newMethodInvocation();
    atomicallInvoke.setExpression(parenthesizedExpression);
    atomicallInvoke.setName(ast.newSimpleName("atomicallyInvoke"));
    atomicallInvoke.arguments().add(ast.newSimpleName("run"));
    rez.add(ast.newExpressionStatement(atomicallInvoke));

    if (getReturnType().getTypeName() .equals("void")==false) {
      VariableDeclarationFragment resultDeclarationFragment = ast.newVariableDeclarationFragment();
      resultDeclarationFragment.setName(ast.newSimpleName(ResultVarName));
      resultDeclarationFragment.setInitializer(ast.newQualifiedName(ast.newSimpleName(resultArray), ast.newSimpleName("argvalue")));
      VariableDeclarationStatement resultDeclarationStatement =ast.newVariableDeclarationStatement(resultDeclarationFragment);
      resultDeclarationStatement.setType(getReturnType().getType(ast));
      rez.add(resultDeclarationStatement);
    }

    return rez;
  }


  private List<Statement> getTransStatement() {
    List<String> arguments = preparePars().stream().map(item -> item.getItem1()).collect(Collectors.toList());

    MethodInvocation bizActionInvocation = ast.newMethodInvocation();
    bizActionInvocation.setName(ast.newSimpleName(bizMgrAction.getCode()));
    bizActionInvocation.setExpression(ast.newSimpleName(VariableName));
    for (String arg : arguments ) {
      bizActionInvocation.arguments().add(ast.newSimpleName(arg));
    }

    if (getReturnType().getTypeName().equals("void")==false) {
      VariableDeclarationFragment variableDeclarationFragment = ast.newVariableDeclarationFragment();
      variableDeclarationFragment.setName(ast.newSimpleName(ResultVarName));
      variableDeclarationFragment.setInitializer(bizActionInvocation);
      VariableDeclarationStatement variableDeclarationStatement =ast.newVariableDeclarationStatement(variableDeclarationFragment);
      variableDeclarationStatement.setType(getReturnType().getType(ast));
      return Arrays.asList(variableDeclarationStatement);
    }
    return Arrays.asList(ast.newExpressionStatement(bizActionInvocation));
  }
}
